#define WIN32_LEAN_AND_MEAN
#include <stdlib.h>
#include <stdio.h>
#include <windows.h>
#include <wchar.h>
#include "pginstca.h"
#include "pgmsi.h"
#include "locale.h"


/*
 * List of all supported database encodings and their corresponding windows 
 * charset codes. Taken from:
 * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/intl/unicode_81rn.asp
 */
_db_encoding
database_encodings[] = 
{
	{"EUC_JP", "20932"},	  /* Japanese EUC(JIS X 0208-1990 & 0212-1990) */
	{"EUC_KR", "51949"},	  /* Korean EUC */
	
	{"ISO_8859_5", "28595"},  /* Latin/Cyrillic */
	{"ISO_8859_6", "28596"},  /* Latin/Arabic */
	{"ISO_8859_7", "28597"},  /* Latin/Greek */
	{"ISO_8859_8", "28598"},  /* Latin/Hebrew */
	
	{"JOHAB", "1361"},		/* Korean EUC (Hangle base) */
	{"KOI8-R", "20866"},	  /* KOI8-R(U) */

	{"LATIN1", "28591"},	  /* ISO 8859-1	/ECMA 94 (Latin alphabet no.1) */
	{"LATIN2", "28592"},	  /* ISO 8859-2	/ECMA 94 (Latin alphabet no.2) */
	{"LATIN3", "28593"},	  /* ISO 8859-3	/ECMA 94 (Latin alphabet no.3) */
	{"LATIN4", "28594"},	  /* ISO 8859-4	/ECMA 94 (Latin alphabet no.4) */
	{"LATIN5", "28599"},	  /* ISO 8859-9	/ECMA 128 (Latin alphabet no.5) */

	{"LATIN9", "28605"},	  /* ISO 8859-15 (Latin alphabet no.9) */
	
	{"MULE_INTERNAL", ""},
	/*
	 * SQL_ASCII is added by default in the Wix code now to ensure
	 * we pass the validation tests.
	 *
	 *  {"SQL_ASCII", ""},
	 */
	
	{"WIN866", "866"},		/* ALT */
	{"WIN874", "874"},		/* Thai */
	{"WIN1250", "1250"},
	{"WIN1251", "1251"},
	{"WIN1252", "1252"},
	{"WIN1256", "1256"},	  /* Arabic */
	{"WIN1258", "1258"},	  /* TCVN (Vietnamese) */

/*
 * The commented-out encodings are not available as windows codepages.
 * UTF-8 (win codepage 65001) is not a real win32 codepage and can only be
 * used with WideCharToMultiByte() and MultiByteToWideChar() API calls, as
 * described here: http://www.sharmahd.com/tm/codepages.html (see
 * Additional Remarks at the bottom of the page). However, in 8.1 we have
 * workaround code for this in the backend. There are several other
 * encodings that are still not supported, though.
 *
 * By removing unsupported encodings from the installer, we prevent people
 * from selecting a combination which would cause initdb to use a different
 * value instead, but they can still create databases using one of those
 * locales by issuing CREATE DATABASE foo ENCODING 'EUC_CN' in psql.
 */

//	{"EUC_CN", ""},		   /* Chinese EUC */
//	{"EUC_TW", ""},		   /* Taiwan EUC */
//	{"LATIN6", ""},		   /* ISO 8859-10/ECMA 144 (Latin alphabet no.6) */
//	{"LATIN7", ""},		   /* ISO 8859-13 (Latin alphabet no.7) */
//	{"LATIN8", ""},		   /* ISO 8859-14 (Latin alphabet no.8) */
//	{"LATIN10", ""},		  /* ISO 8859-16/ASRO SR 14111 (Latin alphabet no.10) */

	{"UTF8", "65001"}
};

const int num_database_encodings = sizeof(database_encodings)/sizeof(database_encodings[0]);

/*
 * This is client encoding(used for PGCLIENTENCODING.)
 */

_db_encoding
client_encodings[] =
{
	{ "EUC_JP", "51932" },
	{ "EUC_CN", "51936" },
	{ "EUC_KR", "51949" },
	{ "EUC_TW", "51950" },
	{ "JOHAB", "1361" },
//	{ "MULE_INTERNAL","MULE_INTERNAL" }, /* No codepage */
	{ "LATIN1", "28591" },
	{ "LATIN2", "28592" },
	{ "LATIN3", "28593" },
	{ "LATIN4", "28594" },
	{ "LATIN5", "28595" },
	{ "LATIN6", "28596" },
	{ "LATIN7", "28597" },
	{ "LATIN8", "28598" },
	{ "LATIN9", "28599" },
//	{ "LATIN10", "LATIN10" }, /* No codepage */
	{ "WIN1256", "1256" },
	{ "WIN1258", "1258" },
	{ "WIN866", "866" },
	{ "WIN874", "874" },
	{ "KOI8", "20866" },
	{ "WIN1251", "1251" },
	{ "WIN1252", "1252" },
	{ "ISO_8859_5", "28595" },
	{ "ISO_8859_6", "28596" },
	{ "ISO_8859_7", "28597" },
	{ "ISO_8859_8", "28598" },
	{ "WIN1250", "1250" },
	{ "WIN1253", "1253" },
	{ "WIN1254", "1254" },
	{ "WIN1255", "1255" },
	{ "WIN1257", "1257" },
//	{ "EUC_JIS_2004", "EUC_JIS_2004"}, /* No codepage */
	{ "SJIS", "932" },
	{ "BIG5", "950" },
	{ "GBK",  "936" },
	{ "UHC", "949" },
	{ "GB18030", "54936" },
//	{ "SHIFT_JIS_2004", "SHIFT_JIS_2004" }, /* No codepage */
//	{ "SQL_ASCII", "0" }
	{ "UTF8", "65001" }
};

const int num_client_encodings = sizeof(client_encodings)/sizeof(client_encodings[0]);

typedef struct _locale_node locale_node;
struct _locale_node
{
	char *name; /* displayed name (form: Language, Region) */
	char *value; /* value used for initdb (form: Language_Region) */
	locale_node *next;
};

/* List of all locales supported by the system */
static 
locale_node *locales_list = NULL;

/* Default locale, if found */
static char *default_locale = NULL;

/*
 * translates received lpLocaleString to a proper locale name and appends it
 * at the end of locales_list if it is not yet a member of the list
 */
static
BOOL CALLBACK process_installed_locales(LPTSTR lpLocaleString) 
{
	unsigned long locale_id;
	int len_language;
	int len_country;
	char *language;
	char *country;
	locale_node *node;
	locale_node *locale;
	locale_node *cnlocale = NULL;
	int len_locale_name;
	int len_locale_value;
	
	locale_id = strtoul(lpLocaleString, NULL, 16);
	
	len_language = GetLocaleInfo(locale_id, LOCALE_SENGLANGUAGE, NULL, 0);
	len_country = GetLocaleInfo(locale_id, LOCALE_SENGCOUNTRY, NULL, 0);
	
	language = (char *)malloc((len_language + 1) * sizeof(char));
	country  = (char *)malloc((len_country + 1) * sizeof(char));
	locale = (locale_node *)malloc(sizeof(locale_node));
	
	if (!language || !country || !locale) 
	{
		MsgBox(0, NULL, L"Failed to allocate memory!");
		return FALSE;
	}

	ZeroMemory(language, (len_language+1) * sizeof(char));
	ZeroMemory(country, (len_country+1) * sizeof(char));
	
	GetLocaleInfo(locale_id, LOCALE_SENGCOUNTRY, country, len_country);
	GetLocaleInfo(locale_id, LOCALE_SENGLANGUAGE, language, len_language);
	
	locale->next = NULL;

	len_locale_name = len_country + len_language + 2;
	len_locale_value = len_country + len_language + 1;
		
	locale->name = (char *)malloc((len_locale_name + 1) * sizeof(char));
	locale->value = (char *)malloc((len_locale_value + 1) * sizeof(char));
	
	if (!locale->name || !locale->value) 
	{
		MsgBox(0, NULL, L"Failed to allocate memory!");
		return FALSE;
	}

	/* Safe to use sprintf, since we have already adjusted the buffer size */
	//sprintf(locale->name, "%s, %s", language, country);
	//sprintf(locale->value, "%s_%s", language, country);

	if (strcmp("People's Republic of China", country))
	{
		sprintf(locale->name, "%s, %s", language, country);
		sprintf(locale->value, "%s_%s", language, country);
	}
	else
	{
		sprintf(locale->name, "Chinese, China");
		sprintf(locale->value, "Chinese_China");
/*
		cnlocale = (locale_node *)malloc(sizeof(locale_node));
		if (cnlocale)
		{
			cnlocale->next = NULL;
			cnlocale->name = (char *)malloc((15) * sizeof(char));
			cnlocale->value = (char *)malloc((14) * sizeof(char));
			sprintf(cnlocale->name, "Chinese, China");
			sprintf(cnlocale->value, "Chinese_China");
		}
*/
	}

	/* Store away default locale if this is it */
	if (locale_id == GetUserDefaultLCID())
		default_locale = strdup(locale->value);
	
	if (locales_list == NULL)
	{
		locales_list = locale;
/*
		if (cnlocale)
		{
			locale->next = cnlocale;
		}
*/
	}
	else 
	{
		node  = locales_list;
		
		while (1) {
			/* 	we need to avoid duplicate values, otherwise the 
				installer exits with an error */
			if (strcmp(node->name, locale->name) == 0)
			{
				free(locale->name);
				free(locale->value);
				free(locale);
/*
				if (cnlocale)
				{
					node  = locales_list;
					locale = cnlocale;
					cnlocale = NULL;
					continue;
				}
*/
				break;
			}
			
			if (node->next == NULL)
			{
				node->next = locale;
/*
				if (cnlocale)
				{
					node  = locales_list;
					locale = cnlocale;
					cnlocale = NULL;
					continue;
				}
*/
				break;
			}
			else
				node = node->next;

		} 
	}
	
	free(language);
	free(country);
	
	return TRUE;
}


/*
 * inserts values into locale and encoding combo boxes, 
 * located on initdb config panel
 */
__declspec(dllexport)
UINT __stdcall GetAvailableLocales(MSIHANDLE hInstall) 
{
	MSIHANDLE database;
	MSIHANDLE view;
	MSIHANDLE rec;
	int position;
	locale_node *locale;
	locale_node *templocale;
	char wincodepage[128];
	int codepage = num_client_encodings;

	// Bug Fix: Honor ENCODING and LOCALE from command line for silent mode
	char _locale[256];
	char _encoding[256];
	char _clencode[256];
	char _uilevel[256];
	DWORD szbuf = sizeof(_locale)-1;
	int fullmode=0;
	int cmdline_enc=1;
	int cmdline_clencode=1;
	int cmdline_loc=1;
	
	// Get locale/encoding from existing properties (can be passed from command line)
	ZeroMemory(_locale,sizeof(_locale));
	ZeroMemory(_encoding,sizeof(_encoding));
	ZeroMemory(_clencode,sizeof(_clencode));
	ZeroMemory(_uilevel,sizeof(_uilevel));
	
	szbuf = sizeof(_locale);
	if (MsiGetPropertyA(hInstall,"LOCALE", _locale, &szbuf) != ERROR_SUCCESS)
	{
		MsgBox(0,NULL,L"Failed to retrieve the 'LOCALE' property.");
		return ERROR_SUCCESS;
	}

	szbuf = sizeof(_encoding);
	if (MsiGetPropertyA(hInstall,"ENCODING", _encoding, &szbuf) != ERROR_SUCCESS)
	{
		MsgBox(0,NULL,L"Failed to retrieve the 'ENCODING' property.");
		return ERROR_SUCCESS;
	}

	szbuf = sizeof(_clencode);
	if (MsiGetPropertyA(hInstall,"CLENCODE", _clencode, &szbuf) != ERROR_SUCCESS)
	{
		MsgBox(0,NULL,L"Failed to retrieve the 'CLENCODE' property.");
		return ERROR_SUCCESS;
	}

	szbuf = sizeof(_uilevel);
	if (MsiGetPropertyA(hInstall,"UILevel", _uilevel, &szbuf) != ERROR_SUCCESS)
	{
		MsgBox(0,NULL,L"Failed to retrieve the 'UILEVEL' property.");
		return ERROR_SUCCESS;
	}
	
	// If LOCALE, ENCODING and CLENCODE are NOT 'DEFAULT', it means that client has passed them as command line.
	// if they are indeed DEFAULT, set them to default values ENCODING=SQL_ASCII, LOCALE=C and CLENCODE=SQL_ASCII
	
	if(!strncmp(_locale,"DEFAULT",7))
	{
		MsiSetPropertyA(hInstall, "LOCALE", "C");
		cmdline_loc=0;
	}

	if(!strncmp(_encoding,"DEFAULT",7))
	{
		MsiSetPropertyA(hInstall, "ENCODING", "SQL_ASCII");
		cmdline_enc=0;
	}
	
	if(!strncmp(_clencode,"DEFAULT",7))
	{
		MsiSetPropertyA(hInstall, "CLENCODE", "SQL_ASCII");
		cmdline_clencode=0;
	}
	
	// Now determine if we are running full mode i.e. UILevel=5
	if(!strncmp(_uilevel,"5",1)) 
	{
	   fullmode=1;
	}
	
	if (!EnumSystemLocales(&process_installed_locales, LCID_INSTALLED))
	{
		MsgBox(0, NULL, L"Failed to enumerate system locales!");
		return ERROR_INSTALL_FAILURE;
	}


	database = MsiGetActiveDatabase(hInstall);
	MsiDatabaseOpenViewA(database, "SELECT * FROM ComboBox", &view);

 	// We only want to create ENCODING, LOCALE and CLENCODE combo view if we are running in full mode.
 	// or the command line is not specified
 	if (fullmode == 1 || (fullmode == 0 && cmdline_enc ==0))
 	{
		for (position=0; position<num_database_encodings; position++)
		{
			rec = MsiCreateRecord(4);

			MsiRecordSetStringA(rec, 1, "ENCODING");
			MsiRecordSetInteger(rec, 2, position);
			MsiRecordSetStringA(rec, 3, database_encodings[position].name);
			MsiRecordSetStringA(rec, 4, database_encodings[position].name);
	
			MsiViewModify(view, MSIMODIFY_INSERT_TEMPORARY, rec);
			MsiCloseHandle(rec);
	
		}
	 }
	 
	 // Only determine the locale is we are in full mode or the command line is not specified.
	 if (fullmode == 1 || (fullmode == 0 && cmdline_loc == 0))
	 {
		position = 0;
		locale = locales_list;
	
		/* insert C locale first */
		rec = MsiCreateRecord(4);
	
		MsiRecordSetStringA(rec, 1, "LOCALE");
		MsiRecordSetInteger(rec, 2, ++position);
		MsiRecordSetStringA(rec, 3, locale->value);
		MsiRecordSetStringA(rec, 4, locale->name);
	
		MsiViewModify(view, MSIMODIFY_INSERT_TEMPORARY, rec);
		MsiCloseHandle(rec);

		while (locale != NULL)
		{
			rec = MsiCreateRecord(4);

			MsiRecordSetStringA(rec, 1, "LOCALE");
			MsiRecordSetInteger(rec, 2, ++position);
			MsiRecordSetStringA(rec, 3, locale->value);
			MsiRecordSetStringA(rec, 4, locale->name);

			MsiViewModify(view, MSIMODIFY_INSERT_TEMPORARY, rec);
			MsiCloseHandle(rec);

			templocale = locale;
			locale = locale->next;

			free(templocale->name);
			free(templocale->value);
			free(templocale);
		}
 	}

	if (fullmode == 1 || (fullmode == 0 && cmdline_clencode == 0))
	{
    		/* CLIENT ENCODING */

    		ZeroMemory(wincodepage, sizeof(wincodepage));
    		_snprintf(wincodepage, sizeof(wincodepage)-1, "%d", GetACP());

    		for (position=0; position<num_client_encodings; position++)
    		{
    			rec = MsiCreateRecord(4);

    			MsiRecordSetStringA(rec, 1, "CLENCODE");
    			MsiRecordSetInteger(rec, 2, position);
    			MsiRecordSetStringA(rec, 3, client_encodings[position].name);
    			MsiRecordSetStringA(rec, 4, client_encodings[position].name);

    			MsiViewModify(view, MSIMODIFY_INSERT_TEMPORARY, rec);
    			MsiCloseHandle(rec);

    			if (!strcmp(client_encodings[position].windows_code, wincodepage))
    				codepage = position;
    		}
    
    		MsiViewClose(view);
    		MsiCloseHandle(database);
	}

 	// We always want to determine default_locale if we are running in fullmode.
 	// We should also determine default_locale if command line argument for ENCODING is not specified in quite mode
 	if(fullmode==1 || (fullmode == 0 && cmdline_enc==0))
 	{
		if (default_locale)
		{
			// Default to C.UTF8 in Japan, per Hiroshi Saito
			if (!strncmp(default_locale, "Japanese", 8))
			{
				// Only set if either full mode or command line locale is not specified
 				if(fullmode == 1 || (fullmode == 0 && cmdline_loc == 0))
					MsiSetPropertyA(hInstall, "LOCALE", "C");

				// Only set if either full mode or command line encoding is not specified
 				if(fullmode == 1 || (fullmode == 0 && cmdline_enc == 0))
					MsiSetPropertyA(hInstall, "ENCODING", "UTF8");
			}
			else
			{
				// Only set if either full mode or command line locale is not specified
 				if(fullmode == 1 || (fullmode == 0 && cmdline_loc == 0))
					MsiSetPropertyA(hInstall, "LOCALE", default_locale);
	
				// Attempt to set the database encoding to the same value as we got from the current
				// locale. If we can't, default to UTF8.
				if(fullmode == 1 || (fullmode == 0 && cmdline_enc == 0 && cmdline_clencode == 0))
				{
					MsiSetPropertyA(hInstall, "ENCODING", "UTF8");
					for (position=0; position<num_database_encodings; position++)
					{
						if (!strcmp(client_encodings[codepage].name, database_encodings[position].name))
						{
							MsiSetPropertyA(hInstall, "ENCODING", database_encodings[position].name);
							continue;
						}
					}
				}
			}
			// Only set default if either we are in full mode or command line client encoding is not specified */
			if(fullmode == 1 || (fullmode == 0 && cmdline_clencode == 0))
				MsiSetPropertyA(hInstall, "CLENCODE", client_encodings[codepage].name);
		}
	} // Full mode

	return ERROR_SUCCESS;
}
